# -*- coding: utf-8 -*-

import logging
import time
#from django.db import transaction

import wolfox.foxit.source.reader as reader
from wolfox.foxit.base.tutils import linelog
from wolfox.foxit.dune.models import PowerInfo,Exchange,StockType,Catalog

logger = logging.getLogger("source.transfer")

class Transfer(object):
    ''' 常用功能的集成类,是一个基本没有自己逻辑的包装类 '''
    def __init__(self,store,conn,begin=0,end=99999999):
        self.store = store
        self.conn = conn
        self.begin = begin
        self.end = end
        self.q_file_ranges=[]   #元素应当为(filename,begin,end)
        self.h_file_ranges=[]   #元素应当为(filename,begin,end)        
        self.x_filenames=[]
        self.r_filenames=[]
        self.c_filenames=[]

    def set_quote_params(self,extractor,filenames):
        ''' 分析家数据存在问题，对数据的批处理导入有影响. 如20080701的日数据中，包含了20060x0x的某个已经退市股票的数据
            虽然如此，对小范围文件(特别是单个文件)的导入，仍可以采用self中设置的begin,end
            已经不用
        '''
        self.q_extractor = extractor
        self.q_file_ranges = [ (filename,self.begin,self.end) for filename in filenames]

    def set_quote_params2(self,extractor,file_ranges):
        ''' 分析家数据存在问题，对数据的批处理导入有影响. 如20080701的日数据中，包含了20060x0x的某个已经退市股票的数据
            因此大规模批量导入时，每个文件都需要单独限定时间段,避免早先的历史数据影响导入
        '''
        self.q_extractor = extractor
        self.q_file_ranges = file_ranges

    def set_hour_quote_params2(self,extractor,file_ranges):
        ''' 分析家数据存在问题，对数据的批处理导入有影响. 如20080701的日数据中，包含了20060x0x的某个已经退市股票的数据
            因此大规模批量导入时，每个文件都需要单独限定时间段,避免早先的历史数据影响导入
        '''
        self.h_extractor = extractor
        self.h_file_ranges = file_ranges

    def set_xinfo_params(self,extractor,filenames):
        self.x_extractor = extractor
        self.x_filenames = filenames

    def set_report_params(self,extractor,filenames):
        self.r_extractor = extractor
        self.r_filenames = filenames

    def set_code_params(self,creader,filenames):
        self.c_reader = creader
        self.c_filenames = filenames
    
    def init_catalog(self,code_reader,code_filenames,catalog_reader,catalog_targets):
        init_catalog(self.store,code_reader,code_filenames,catalog_reader,catalog_targets)

    def sync(self):
        bt = time.time()
        new_codes = sync_codes(self.store,self.c_reader,self.c_filenames)
        self.store.transfer_xquotes2(self.conn,new_codes)    #对之前既有但没有code的行情数据的转换
        self.store.transfer_hour_xquotes2(self.conn,new_codes)    #对之前既有但没有code的行情数据的转换
        #迟到的新代码对除权数据没有影响，因为之前代码没有，数据进不了PowerInfo表，有代码之后，老数据仍然能进
        print u'代码同步耗时:%s' % (time.time() - bt)
        xbegin,xend = 99999999,0
        ct = time.time()
        linelog('begin quotes transfer')
        for filename,fbegin,fend in self.q_file_ranges:
            print u'导入文件%s' % filename
            begin,end = sync_quotes(self.store,self.conn,filename,self.q_extractor,fbegin,fend)
            xbegin = begin if begin < xbegin else xbegin
            xend = end if end > xend else xend
        print u'行情数据导入耗时:%s' % (time.time() - ct)            
        linelog('begin xinfos transfer')        
        dt = time.time()
        for filename in self.x_filenames:
            sync_xinfos(self.store,filename,self.x_extractor,xbegin,xend)
        print u'除权数据导入耗时:%s' % (time.time() - dt)            
        et = time.time()
        linelog('begin reports transfer')                
        for filename in self.r_filenames:
            sync_reports(self.store,filename,self.r_extractor,xbegin,xend)
        print u'财务数据同步耗时:%s' % (time.time() - et)            
        linelog('begin xit')        
        ft = time.time()
        #xit_end = self.store.get_latest_xquote_day(self.conn)
        xit(self.store,self.conn,0) #除权所有到此未除权的项
        #_xit2(self.store,self.conn,['SH600654'],0)
        #_xit2(self.store,self.conn,['SH600654'],19950526)
        print u'除权耗时:%s' % (time.time() - ft)        
        self.sync60()
        return xbegin,xend

    def sync60(self):
        xbegin,xend = 2200000000,0
        ct = time.time()
        for filename,fbegin,fend in self.h_file_ranges:
            print u'导入文件%s' % filename
            begin,end = sync_hour_quotes(self.store,self.conn,filename,self.h_extractor,fbegin,fend)
            xbegin = begin if begin < xbegin else xbegin
            xend = end if end > xend else xend
        print u'60分钟数据导入耗时:%s' % (time.time() - ct)            
        ft = time.time()
        xit_hour(self.store,self.conn,0) #除权所有到此未除权的项        
        print u'60分钟除权耗时:%s' % (time.time() - ft)        
        return xbegin,xend

#公开的函数
def sync_quotes(store,conn,filename,extractor,begin,end):  
    qreader = reader.Reader(extractor,_create_date_filter(begin,end))
    quotes,xbegin,xend = _read_quotes(filename,qreader)
    print u'数据读入完毕,共%s条记录' % len(quotes)
    #print xbegin,store.get_latest_squote_day(conn),store.get_latest_xquote_day(conn)
    if xbegin <= store.get_latest_squote_day(conn) or xbegin <= store.get_latest_xquote_day(conn): 
        from sqlite3 import IntegrityError
        raise IntegrityError,u'要导入数据的日期小于已有数据的日期'
    logger.debug(u'开始插入数据')
    #store.add_squotes(conn,quotes)
    store.add_quotes(conn,quotes)
    print u'文件%s倒入完毕.' % filename
    #print u'文件%s倒入完毕，开始转换....' % filename
    #store.transfer_xquotes(conn,xbegin,xend)
    return xbegin,xend

def sync_hour_quotes(store,conn,filename,extractor,begin,end):  
    print begin,end
    qreader = reader.Reader(extractor,_create_date_filter(begin*100,end*100))
    quotes,xbegin,xend = _read_quotes(filename,qreader)
    print 'len(quotes)=%s,xbegin=%s,xend=%s'%(len(quotes),xbegin,xend)
    print u'数据读入完毕'
    #print store.get_latest_hour_squote_day(conn),store.get_latest_hour_xquote_day(conn)
    #print xbegin,store.get_latest_squote_day(conn),store.get_latest_xquote_day(conn)
    if xbegin <= store.get_latest_hour_squote_day(conn) or xbegin <= store.get_latest_hour_xquote_day(conn): 
        from sqlite3 import IntegrityError
        raise IntegrityError,u'要导入数据的日期小于已有数据的日期'
    logger.debug(u'开始插入数据')
    #store.add_squotes(conn,quotes)
    store.add_hour_quotes(conn,quotes)
    print u'文件%s倒入完毕.' % filename
    #print u'文件%s倒入完毕，开始转换....' % filename
    #store.transfer_xquotes(conn,xbegin,xend)
    return xbegin,xend

def sync_xinfos(store,filename,extractor,begin=0,end=99999999):
    ''' 同步除权信息'''
    return _sync_records(store.get_xinfo_keys,store.add_xinfos,filename,extractor,begin,end)

def sync_reports(store,filename,extractor,begin=0,end=99999999):
    ''' 同步年报信息'''
    return _sync_records(store.get_report_keys,store.add_reports,filename,extractor,begin,end)

def xit(store,conn,begin=0): 
    ''' 时间段除权
        
        begin/end:除权交易数据的起止日
    '''
    xit_end = store.get_latest_xquote_day(conn)    
    xinfos = list(PowerInfo.objects.filter(execute_day__range = (begin,xit_end)).filter(day_state = 0).order_by('execute_day')) #必须先转换成list以读完记录，否则当记录数超过100(估计是缓存数量)时，后续处理(xinfo.save)时会因为表锁死而无法save
    print u'待除权项数量:%s' % len(xinfos)
    store.xit(conn,xinfos)

def xit_hour(store,conn,begin=0): 
    ''' 时间段除权
        
        begin/end:除权交易数据的起止日
    '''
    xit_end = store.get_latest_hour_xquote_day(conn)    
    xinfos = list(PowerInfo.objects.filter(execute_day__range = (begin,xit_end)).filter(hour_state = 0).order_by('execute_day')) #必须先转换成list以读完记录，否则当记录数超过100(估计是缓存数量)时，后续处理(xinfo.save)时会因为表锁死而无法save
    print u'待除权项数量:%s' % len(xinfos)
    store.xit_hour(conn,xinfos)

def sync_codes(store,creader,filenames):
    ''' 同步代码 
    
        store: 底层数据服务对象
        creader: 文件读取对象
        filenames: [...] 代码文件
    '''
    ex_map = dict([ (exchange.abbr_code,exchange) for exchange in Exchange.objects.all()])
    st_map = dict([ (stype.name,stype) for stype in StockType.objects.all()])
    codes = []
    for filename in filenames : 
        codes.extend(creader.read_codes(filename))
    codes = [ (ex_map[code[0]],code[1],code[2],st_map[code[3]]) for code in codes if code[0] in ex_map and code[-1] in st_map ] #过滤掉市场和种类之外的代码
    new_codes = store.update_codes(codes)
    #print new_codes
    return new_codes

def init_catalog(store,code_reader,code_filenames,catalog_reader,catalog_targets):
    ''' 连带脚本. 先执行sync_codes,然后执行init_catalog
        这个脚本只能在初始化的时候执行一次。否则因为这里同步了code,但没有对那些行情数据进行补转换，会导致隐藏的错误

        code_reader:    文件读取对象
        code_filenames: [...] 代码文件
        catalog:reader: CatalogReader的实例
        catalog_targets: 板块文件所在目录或文件列表

    '''
    sync_codes(store,code_reader,code_filenames)
    return _init_catalog(store,catalog_reader,catalog_targets)


####以下为内务函数或普通情形下不建议使用的函数(如_xit2)#######
def sync_quotes2(store,conn,filename,extractor,begin,end):  #这里是先删除再导入. 容易误操作,废弃?
    qreader = reader.Reader(extractor,_create_date_filter(begin,end))
    quotes,xbegin,xend = _read_quotes(filename,qreader)
    #for quote in quotes: print quote
    store.remove_squotes(conn,begin,end)
    store.remove_xquotes(conn,begin,end)
    store.add_squotes(conn,quotes)
    store.transfer_xquotes(conn,xbegin,xend)
    return xbegin,xend

def _read_quotes(filename,qreader):   #便捷函数,建议用包裹函数sync_quotes替代
    return _read_records(filename,qreader)

def _read_xinfos(filename,reader):   #便捷函数. 但建议用sync_xinfos替代
    return _read_records(filename,reader)

def _read_reports(filename,reader):  #便捷函数. 但建议用sync_reports替代
    return _read_records(filename,reader)

def _create_date_filter(begin,end):
    if(begin == 0 and end == 99999999):
        return reader.NullFilter()
    return reader.DateFilter(begin,end)

def _create_filter(begin,end,stocks):
    datefilter = reader.DateFilter(begin,end)
    return reader.CodeFilter(stocks,datefilter)

def _read_records(filename,reader):
    ''' 读取记录

        filename:文件名
        reader:读取对象
    '''
    records = set(reader.read(filename)) #去重. 这里有个前提条件是同一天的除权记录不会被数据来源刻意的分为两条.
                                         #因为这里的去重实际上判断标准是 股票名tstock+日期tdate，其它字段不论
    logger.debug(u"文件 %s 包含 %s 条记录" % (filename,len(records)))
    if(not records):
        logger.debug(u'没有更新的记录')
        return [],0,0
    #records = [ record for record in records if record.is_valid()]  #去掉错误数据    
    #logger.debug(u"transfer:去除错误数据后文件 %s 中有 %s 条记录" % (filename,len(records)))    
    begin,end = min(records).tdate,max(records).tdate+1
    logger.debug(u"记录起止日期:%s -- %s" % (begin,end))
    return records,begin,end


def _xit2(store,conn,stocks,begin=0,end=99999999):
    ''' 时间段内对特定股票集合除权
        
        begin/end:除权交易数据的起止日
        stocks为标准code集合
    '''
    xit_end = store.get_latest_xquote_day(conn)
    xinfos = PowerInfo.objects.filter(execute_day__range = (begin,xit_end)) \
              .filter(tstock__code__in=stocks).filter(day_state = 0).order_by('execute_day')
    store.xit(conn,xinfos)

def _sync_records(key_func,add_func,filename,extractor,begin,end):
    rreader = reader.Reader(extractor,_create_date_filter(begin,end))
    #print filename,rreader,begin,end
    src_records,rbegin,rend = _read_records(filename,rreader)
    exist_keys= set(key_func(rbegin,rend))
    #print src_records[0].tstock,src_records[0].tdate,type(src_records[0])
    new_records = [ record for record in src_records if (record.tstock,record.tdate) not in exist_keys]
    #print new_records
    #print new_records,add_func
    add_func(new_records)
    return len(new_records),rbegin,rend


def _init_catalog(store,creader,targets):
    ''' 板块初始化
    
        creader: CatalogReader的实例
        targets: 板块文件所在目录或文件列表
        只能执行一次。在第一次sync_codes之后，因为需要用到合法的StockCode信息 
        需要适当处理股票代码不存在的情况(由add_stock_to_catalog3处理)
        没有处理板块不存在的情况. 因此调用者需要确保这个情况不存在
    '''
    c_map = dict([ (c.name,c) for c in Catalog.objects.all()])    
    catalogss = []
    for target in targets:
        catalogss.extend(creader.read_catalogs(target))
    catalogss = [ entry for entry in catalogss if entry[0] in c_map ]
    map(lambda entry:store.add_stock_to_catalog3(*entry),catalogss)
    return [name for name,catalogs in catalogss]
    

